package com.yjzx.util.common.util.number;

import com.yjzx.util.common.util.StringUtils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;

/**
 * @author yjzx
 * @date 2023-04-26
 * @Description 精确数字计算
 */
public class BigDecimalUtils {
    public static final BigDecimal ZERO = BigDecimal.ZERO;
    public static final BigDecimal ONE = BigDecimal.ONE;
    public static final BigDecimal TEN = BigDecimal.TEN;
    public static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
    public static final BigDecimal ONE_THOUSAND = new BigDecimal("1000");
    public static final BigDecimal TEN_THOUSAND = new BigDecimal("10000");

    public static boolean isBigDecimalEmpty(BigDecimal bigDecimal) {
        return bigDecimal == null || ZERO.compareTo(bigDecimal) == 0;
    }

    public static boolean isNumberEmpty(Number number) {
        return number == null || number.doubleValue() == 0;
    }

    /**
     * 字符串转换大精确小数
     *
     * @param o
     * @return
     */
    public static BigDecimal getBigDecimal(String o) {
        if (StringUtils.isBlank(o)) {
            return ZERO;
        }
        double value = Double.parseDouble(o);
        return (value >= Double.MAX_VALUE) ? new BigDecimal(o) : getBigDecimal(value);
    }

    public static BigDecimal getBigDecimal(Number o, BigDecimal defaultNum) {
        if (o == null) {
            return defaultNum;
        } else {
            return getBigDecimal(o);
        }
    }

    /**
     * Number转换大精确小数
     *
     * @param o
     * @return
     */
    public static BigDecimal getBigDecimal(Number o) {
        if (o == null) {
            return ZERO;
        } else if (o instanceof BigDecimal) {
            return (BigDecimal) o;
        }
        Double value = o.doubleValue();
        return (value == 0) ? ZERO
                : (value == 100) ? ONE_HUNDRED
                : (value == 1000) ? ONE_THOUSAND
                : (value == 10000) ? TEN_THOUSAND
                : (value == 1) ? ONE
                : (value == 10) ? TEN
                : (o instanceof BigInteger) ? new BigDecimal((BigInteger) o)
                : new BigDecimal(Double.toString(o.doubleValue()));
    }

    public static BigDecimal getByMultiplyNumber(Number dividend, Number divisor, BigDecimal defaultReturn) {
        BigDecimal decimal1 = getBigDecimal(dividend);
        BigDecimal decimal2 = getBigDecimal(divisor);
        if (isBigDecimalEmpty(decimal1) || isBigDecimalEmpty(decimal2)) {
            return defaultReturn;
        } else {
            return decimal1.multiply(decimal2);
        }
    }

    public static BigDecimal getByMultiplyNumber(Number dividend, int scale, BigDecimal decimal) {
        BigDecimal number = getByMultiplyNumber(dividend, scale > 0 ? Math.pow(10, scale) : 1, null);
        if (number == null) {
            return decimal;
        } else {
            return number;
        }
    }

    public static BigDecimal getByDivisionNumber(Number dividend, Number divisor, int scale, BigDecimal defaultReturn, RoundingMode roundingMode) {
        if (dividend != null && dividend.doubleValue() == 0 && divisor != null && divisor.doubleValue() != 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal decimal1 = getBigDecimal(dividend);
        BigDecimal decimal2 = getBigDecimal(divisor);
        return (isBigDecimalEmpty(decimal1) || isBigDecimalEmpty(decimal2)) ? defaultReturn
                : divisor != null && divisor.intValue() == 1 ? decimal1.setScale(scale, roundingMode)
                : decimal1.divide(decimal2, scale, roundingMode);
    }

    public static BigDecimal getByDivisionNumber(Number dividend, int scale, BigDecimal defaultReturn) {
        return getByDivisionNumber(dividend, scale != 0 ? Math.pow(10, scale) : 1, Math.max(scale, 0), defaultReturn, RoundingMode.HALF_UP);
    }

    public static BigDecimal getByDivisionNumber(Number dividend, int scale) {
        return getByDivisionNumber(dividend, scale != 0 ? Math.pow(10, scale) : 1, Math.max(scale, 0), ZERO, RoundingMode.HALF_UP);
    }

    public static String getByDivisionNumber(Number dividend, Number divisor, int scale, String defaultReturn) {
        BigDecimal number = getByDivisionNumber(dividend, divisor, scale, null, RoundingMode.HALF_UP);
        if (number == null) {
            return defaultReturn;
        } else {
            return number.toString();
        }
    }

    public static String getByDivisionNumber(Number dividend, int scale, String defaultReturn) {
        BigDecimal number = getByDivisionNumber(dividend, scale != 0 ? Math.pow(10, scale) : 1, Math.max(scale, 0), null, RoundingMode.HALF_UP);
        if (number == null) {
            return defaultReturn;
        } else {
            return number.toString();
        }
    }

    public static Double getByDivisionDouble(Number dividend, int scale, Double defaultReturn) {
        BigDecimal number = getByDivisionNumber(dividend, scale != 0 ? Math.pow(10, scale) : 1, Math.max(scale, 0), null, RoundingMode.HALF_UP);
        if (number == null) {
            return defaultReturn;
        } else {
            return number.doubleValue();
        }
    }

    public static BigDecimal getByDivisionNumber(Number dividend, Number divisor, int scale, BigDecimal defaultReturn) {
        return getByDivisionNumber(dividend, divisor, scale, defaultReturn, RoundingMode.HALF_UP);
    }


    /**
     * 获取环比增长率或降低率
     *
     * @param previous 第一个数
     * @param next     第二个数
     * @return
     */
    public static Integer getGrowthRateOrReductionRate(Long previous, Long next) {
        return getGrowthRateOrReductionRate(previous, next, 4);
    }

    /**
     * 获取环比增长率或降低率
     *
     * @param previous 第一个数
     * @param next     第二个数
     * @param scale    根据% 2 ‰3 ‱4 需要保留几位
     * @return
     */
    public static int getGrowthRateOrReductionRate(Long previous, Long next, int scale) {
        return (isNumberEmpty(previous) || isNumberEmpty(next)) ? 0 : new BigDecimal(previous - next)
                .divide(getBigDecimal(next), scale, RoundingMode.HALF_UP)
                .multiply(BigDecimal.valueOf(scale > 0 ? Math.pow(10, scale) : 1))
                .setScale(0, RoundingMode.HALF_UP)
                .intValue();
    }
}